Amazon Bedrock Guardrails の ApplyGuardrail API を使って生成 AI 以外のアプリケーションの入出力をフィルタリングする

Amazon Bedrock Guardrails の ApplyGuardrail API を使って生成 AI 以外のアプリケーションの入出力をフィルタリングする

Amazon Bedrock Guardrails は LLM との入出力データをフィルタリングする機能なイメージですが、 ApplyGuardrail を使えば LLM に関わらずフィルタリングできます。
Clock Icon2025.01.03

こんにちは!クラウド事業本部コンサルティング部のたかくに(@takakuni_)です。

Amazon Bedrock Guardrails 使っていますでしょうか。

Amazon Bedrock Guardrails は LLM との入出力データをフィルタリングする機能として、以下のようなイメージをされている方も多いのではないでしょうか。

Untitled(112).png

私もそう思っていたのですが、 LLM を介さず Amazon Bedrock Guardrails を利用できる ApplyGuardrail API と言うものを先日知りました。

https://docs.aws.amazon.com/bedrock/latest/userguide/guardrails-use-independent-api.html

ApplyGuardrail API

ApplyGuardrail API は Amazon Bedrock Guardrails に対して、期待される入出力データを直接渡し、チェックできる API です。

ApplyGuardrail API を利用することで、 LLM を介さず Amazon Bedrock Guardrails の機能を利用できます。

Untitled(111).png

Amazon Bedrock でサポートしていない LLM アプリケーションの実装時にガードレールを利用したいケースや、結合テスト前の簡易的なチェックとして、ガードレールを利用したい時に LLM の利用コストや推論時間のショートカットに使えそうです。

Untitled(110).png

ガードレール単体で API が利用できる。と言うことは、「LLM を使わないシーンでも Amazon Bedrock Guardrails の機能を利用できるようになった。」と解釈しても良いのではないでしょうか。

機密情報フィルター

機密情報フィルターはデータに機密情報が含まれていないかをチェックする機能です。データに機密情報が含まれていた際はデータをマスクする or ブロックする(特定のレスポンスを応答する)ことができます。

たとえば次のようなデータ検出できます。

  • 全般カテゴリ
    • ADDRESS
    • AGE
    • NAME
    • EMAIL
  • 財務カテゴリ
    • CREDIT_DEBIT_CARD_CVV
    • CREDIT_DEBIT_CARD_EXPIRY
    • CREDIT_DEBIT_CARD_NUMBER
  • IT
    • IP_ADDRESS
    • URL
    • AWS_ACCESS_KEY
    • AWS_SECRET_KEY

また正規表現を利用して、機密情報の認識をカスタマイズできます。

  • カスタム
  • 正規表現フィルター – 正規表現を使用して、ガードレールでシリアル番号、予約 ID などを認識し、適宜処理するためのパターンを定義できます。

https://docs.aws.amazon.com/ja_jp/bedrock/latest/userguide/guardrails-sensitive-filters.html

今回は機密情報フィルターを利用してチェックしてみたいと思います。

やってみる

シンプルにオウム返しを行うアプリケーション(プログラム)を作成します。

オウム返しする前に ApplyGuardrail API を実行し、機密情報が含まれていないかチェックし、含まれていたらマスクして返答する処理を実装します。

Untitled(113).png

ApplyGuardrail の API スキーマとドキュメントを確認します。

チェックに引っかかった場合は outputs[0].text に結果が返ってくるようです。

ApplyGuardrail.json
{
   "action": "string",
   "assessments": [
      {
         "contentPolicy": {
            "filters": [
               {
                  "action": "string",
                  "confidence": "string",
                  "filterStrength": "string",
                  "type": "string"
               }
            ]
         },
         "contextualGroundingPolicy": {
            "filters": [
               {
                  "action": "string",
                  "score": number,
                  "threshold": number,
                  "type": "string"
               }
            ]
         },
         "invocationMetrics": {
            "guardrailCoverage": {
               "images": {
                  "guarded": number,
                  "total": number
               },
               "textCharacters": {
                  "guarded": number,
                  "total": number
               }
            },
            "guardrailProcessingLatency": number,
            "usage": {
               "contentPolicyUnits": number,
               "contextualGroundingPolicyUnits": number,
               "sensitiveInformationPolicyFreeUnits": number,
               "sensitiveInformationPolicyUnits": number,
               "topicPolicyUnits": number,
               "wordPolicyUnits": number
            }
         },
         "sensitiveInformationPolicy": {
            "piiEntities": [
               {
                  "action": "string",
                  "match": "string",
                  "type": "string"
               }
            ],
            "regexes": [
               {
                  "action": "string",
                  "match": "string",
                  "name": "string",
                  "regex": "string"
               }
            ]
         },
         "topicPolicy": {
            "topics": [
               {
                  "action": "string",
                  "name": "string",
                  "type": "string"
               }
            ]
         },
         "wordPolicy": {
            "customWords": [
               {
                  "action": "string",
                  "match": "string"
               }
            ],
            "managedWordLists": [
               {
                  "action": "string",
                  "match": "string",
                  "type": "string"
               }
            ]
         }
      }
   ],
   "guardrailCoverage": {
      "images": {
         "guarded": number,
         "total": number
      },
      "textCharacters": {
         "guarded": number,
         "total": number
      }
   },
   "outputs": [
      {
         "text": "string"
      }
   ],
   "usage": {
      "contentPolicyUnits": number,
      "contextualGroundingPolicyUnits": number,
      "sensitiveInformationPolicyFreeUnits": number,
      "sensitiveInformationPolicyUnits": number,
      "topicPolicyUnits": number,
      "wordPolicyUnits": number
   }
}

https://docs.aws.amazon.com/ja_jp/bedrock/latest/APIReference/API_runtime_ApplyGuardrail.html#bedrock-runtime_ApplyGuardrail-response-outputs

リクエストコンテンツに対してガードレールが何もアクションを実行しなかった場合、出力配列は空です。

https://docs.aws.amazon.com/ja_jp/bedrock/latest/userguide/guardrails-use-independent-api.html

ガードレールの作成

ガードレールの作成を行います。サンプルとして日本の郵便番号(ハイフン入り)を正規表現で指定します。動作はマスクにしてみましょう。

2025-01-03 at 21.11.27-Amazon Bedrock  ap-northeast-1@2x.png

まずは、ユーザーからの引数をガードレールに渡し、レスポンスを表示する処理にしてみました。

main.py
import sys
import json
import boto3

GURADRAIL_ID = 'hinvjfl8pwu2'

def main(input: str):
    client = boto3.client('bedrock-runtime')
    response = json.dumps(client.apply_guardrail(
        guardrailIdentifier=GURADRAIL_ID,
        guardrailVersion='DRAFT',
        source='INPUT',
        content=[
            {
                'text': {
                    'text': str(input)
                }
            }
        ]
    ), indent=2, ensure_ascii=False)
    print(response)

if __name__ == "__main__":
    main(sys.argv[1])

おっと、 outputs にデータがないため、うまく機能していないですね。

(guardrails-applyguardrail-api-py3.12) takakuni@ guardrails_applyguardrail_api % python main.py 'クラスメソッドの本社は〒 105-0003 東京都港区西新橋1-1-1 日比谷フォートタワー26階です。'
{
  "ResponseMetadata": {
    "RequestId": "6faaf64f-f835-45ef-949b-7845fe8fe816",
    "HTTPStatusCode": 200,
    "HTTPHeaders": {
      "date": "Fri, 03 Jan 2025 12:20:33 GMT",
      "content-type": "application/json",
      "content-length": "615",
      "connection": "keep-alive",
      "x-amzn-requestid": "6faaf64f-f835-45ef-949b-7845fe8fe816"
    },
    "RetryAttempts": 0
  },
  "usage": {
    "topicPolicyUnits": 0,
    "contentPolicyUnits": 0,
    "wordPolicyUnits": 0,
    "sensitiveInformationPolicyUnits": 0,
    "sensitiveInformationPolicyFreeUnits": 0,
    "contextualGroundingPolicyUnits": 0
  },
  "action": "NONE",
  "outputs": [],
  "assessments": [
    {
      "invocationMetrics": {
        "guardrailProcessingLatency": 140,
        "usage": {
          "topicPolicyUnits": 0,
          "contentPolicyUnits": 0,
          "wordPolicyUnits": 0,
          "sensitiveInformationPolicyUnits": 0,
          "sensitiveInformationPolicyFreeUnits": 0,
          "contextualGroundingPolicyUnits": 0
        },
        "guardrailCoverage": {
          "textCharacters": {
            "guarded": 52,
            "total": 52
          }
        }
      }
    }
  ],
  "guardrailCoverage": {
    "textCharacters": {
      "guarded": 52,
      "total": 52
    }
  }
}

英語にしてみましたが、うまく動いていないです。

(guardrails-applyguardrail-api-py3.12) takakuni@ guardrails_applyguardrail_api % python main.py 'The head office of Classmethod is located at Hibiya Fort Tower 26F, 1-1-1 Nishi-Shimbashi, Minato-ku, Tokyo 105-0003, Japan.'
{
  "ResponseMetadata": {
    "RequestId": "0d45f795-a78e-4a47-97a6-719b9c814428",
    "HTTPStatusCode": 200,
    "HTTPHeaders": {
      "date": "Fri, 03 Jan 2025 12:19:45 GMT",
      "content-type": "application/json",
      "content-length": "619",
      "connection": "keep-alive",
      "x-amzn-requestid": "0d45f795-a78e-4a47-97a6-719b9c814428"
    },
    "RetryAttempts": 0
  },
  "usage": {
    "topicPolicyUnits": 0,
    "contentPolicyUnits": 0,
    "wordPolicyUnits": 0,
    "sensitiveInformationPolicyUnits": 0,
    "sensitiveInformationPolicyFreeUnits": 0,
    "contextualGroundingPolicyUnits": 0
  },
  "action": "NONE",
  "outputs": [],
  "assessments": [
    {
      "invocationMetrics": {
        "guardrailProcessingLatency": 182,
        "usage": {
          "topicPolicyUnits": 0,
          "contentPolicyUnits": 0,
          "wordPolicyUnits": 0,
          "sensitiveInformationPolicyUnits": 0,
          "sensitiveInformationPolicyFreeUnits": 0,
          "contextualGroundingPolicyUnits": 0
        },
        "guardrailCoverage": {
          "textCharacters": {
            "guarded": 124,
            "total": 124
          }
        }
      }
    }
  ],
  "guardrailCoverage": {
    "textCharacters": {
      "guarded": 124,
      "total": 124
    }
  }
}

動作をマスクからブロックにした場合は日本語でも動きました。想定した動作ではないです。

(guardrails-applyguardrail-api-py3.12) takakuni@ guardrails_applyguardrail_api % python main.py 'クラスメソッドの本社は〒 105-0003 東京都港区西新橋1-1-1 日比谷フォートタワー26階です。'
{
  "ResponseMetadata": {
    "RequestId": "ecc3d177-db9d-4f0f-af1e-7e43e01f2a3a",
    "HTTPStatusCode": 200,
    "HTTPHeaders": {
      "date": "Fri, 03 Jan 2025 12:23:35 GMT",
      "content-type": "application/json",
      "content-length": "1043",
      "connection": "keep-alive",
      "x-amzn-requestid": "ecc3d177-db9d-4f0f-af1e-7e43e01f2a3a"
    },
    "RetryAttempts": 0
  },
  "usage": {
    "topicPolicyUnits": 0,
    "contentPolicyUnits": 0,
    "wordPolicyUnits": 0,
    "sensitiveInformationPolicyUnits": 0,
    "sensitiveInformationPolicyFreeUnits": 1,
    "contextualGroundingPolicyUnits": 0
  },
  "action": "GUARDRAIL_INTERVENED",
  "outputs": [
    {
      "text": "申し訳ありませんが、モデルはこの質問に回答できません。"
    }
  ],
  "assessments": [
    {
      "sensitiveInformationPolicy": {
        "regexes": [
          {
            "name": "POST_CODE",
            "match": "105-0003",
            "regex": "\\d{3}-\\d{4}",
            "action": "BLOCKED"
          }
        ]
      },
      "invocationMetrics": {
        "guardrailProcessingLatency": 193,
        "usage": {
          "topicPolicyUnits": 0,
          "contentPolicyUnits": 0,
          "wordPolicyUnits": 0,
          "sensitiveInformationPolicyUnits": 0,
          "sensitiveInformationPolicyFreeUnits": 1,
          "contextualGroundingPolicyUnits": 0
        },
        "guardrailCoverage": {
          "textCharacters": {
            "guarded": 52,
            "total": 52
          }
        }
      }
    }
  ],
  "guardrailCoverage": {
    "textCharacters": {
      "guarded": 52,
      "total": 52
    }
  }
}

source を INPUT から OUTPUT に変更してみました。

main.py
import sys
import json
import boto3

GURADRAIL_ID = 'hinvjfl8pwu2'

def main(input: str):
    client = boto3.client('bedrock-runtime')
    response = json.dumps(client.apply_guardrail(
        guardrailIdentifier=GURADRAIL_ID,
        guardrailVersion='DRAFT',
-       source='INPUT',
+       source='OUTPUT',
        content=[
            {
                'text': {
                    'text': str(input)
                }
            }
        ]
    ), indent=2, ensure_ascii=False)
    print(response)

if __name__ == "__main__":
    main(sys.argv[1])

OUTPUT に変更するとマスクした値で出力されています。想定通りですね。

(guardrails-applyguardrail-api-py3.12) takakuni@ guardrails_applyguardrail_api % python main.py 'クラスメソッドの本社は〒 105-0003 東京都港区西新橋1-1-1 日比谷フォートタワー26階です。'
{
  "ResponseMetadata": {
    "RequestId": "8f18f12b-cd80-4bc0-ac45-ef1ddf1a3e75",
    "HTTPStatusCode": 200,
    "HTTPHeaders": {
      "date": "Fri, 03 Jan 2025 12:25:32 GMT",
      "content-type": "application/json",
      "content-length": "1032",
      "connection": "keep-alive",
      "x-amzn-requestid": "8f18f12b-cd80-4bc0-ac45-ef1ddf1a3e75"
    },
    "RetryAttempts": 0
  },
  "usage": {
    "topicPolicyUnits": 0,
    "contentPolicyUnits": 0,
    "wordPolicyUnits": 0,
    "sensitiveInformationPolicyUnits": 0,
    "sensitiveInformationPolicyFreeUnits": 1,
    "contextualGroundingPolicyUnits": 0
  },
  "action": "GUARDRAIL_INTERVENED",
  "outputs": [
    {
      "text": "クラスメソッドの本社は〒 {POST_CODE} 東京都港区西新橋1-1-1 日比谷フォートタワー26階です。\n"
    }
  ],
  "assessments": [
    {
      "sensitiveInformationPolicy": {
        "regexes": [
          {
            "name": "POST_CODE",
            "match": "105-0003",
            "regex": "\\d{3}-\\d{4}",
            "action": "ANONYMIZED"
          }
        ]
      },
      "invocationMetrics": {
        "guardrailProcessingLatency": 113,
        "usage": {
          "topicPolicyUnits": 0,
          "contentPolicyUnits": 0,
          "wordPolicyUnits": 0,
          "sensitiveInformationPolicyUnits": 0,
          "sensitiveInformationPolicyFreeUnits": 1,
          "contextualGroundingPolicyUnits": 0
        },
        "guardrailCoverage": {
          "textCharacters": {
            "guarded": 52,
            "total": 52
          }
        }
      }
    }
  ],
  "guardrailCoverage": {
    "textCharacters": {
      "guarded": 52,
      "total": 52
    }
  }
}

最後に outputs が含まれるかどうかで条件分岐させてみました。

main.py
import sys
import json
import boto3

GURADRAIL_ID = 'hinvjfl8pwu2'

def main(input: str):
    client = boto3.client('bedrock-runtime')
    response = client.apply_guardrail(
        guardrailIdentifier=GURADRAIL_ID,
        guardrailVersion='DRAFT',
        source='OUTPUT',
        content=[
            {
                'text': {
                    'text': str(input)
                }
            }
        ]
    )
    if len(response["outputs"]) == 0:
        print(input)
    else:
        print(response["outputs"][0]['text'])

if __name__ == "__main__":
    main(sys.argv[1])

うまくマスクが動作していますね。

(guardrails-applyguardrail-api-py3.12) takakuni@ guardrails_applyguardrail_api % python main.py 'クラスメソッドの本社は〒 105-0003 東京都港区西新橋1-1-1 日比谷フォートタワー26階です。'
クラスメソッドの本社は〒 {POST_CODE} 東京都港区西新橋1-1-1 日比谷フォートタワー26階です。

(guardrails-applyguardrail-api-py3.12) takakuni@ guardrails_applyguardrail_api % python main.py 'クラスメソッドの本社は東京都港区西新橋1-1-1 日比谷フォートタワー26階です。'
クラスメソッドの本社は東京都港区西新橋1-1-1 日比谷フォートタワー26階です。

まとめ

「Amazon Bedrock Guardrails の ApplyGuardrail API を使って生成 AI 以外のアプリケーションの入出力をフィルタリングする」でした。

このようなイメージで生成 AI にかかわらず、 Amazon Bedrock Guardrails がうまくハマるケースもあるのではないかと思います。

今回は触れていないですが、コンテンツフィルターを利用して SNS やブログサイトなどの投稿をチェックするアイデアも面白そうな気がします。

このブログがどなたかの参考になれば幸いです。

クラウド事業本部コンサルティング部のたかくに(@takakuni_)でした!

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.